#!/usr/bin/perl
use strict;
use warnings;
use Foundation;
use Mail::IMAPClient;
use Mail::POP3Client;
use Email::MIME::RFC2047::Decoder;
use Email::Address;
use Email::MIME;
use Date::Parse;
use Data::Dumper;
use E3DS::BounceParser;
use E3DS::HelperAppUnixServerSocket;
use E3DS::MailScanner::IMAP;
use E3DS::MailScanner::POP3;
use E3DS::MailScanner::Test;
use constant EX_OK=>0;
use constant SCANNER_PACKAGES=>qw( E3DS::MailScanner::POP3 E3DS::MailScanner::IMAP E3DS::MailScanner::Test );
use constant STATUS_CONNECTING=>"CONNECTING";
use constant STATUS_CONNECTED=>"CONNECTED";
use constant STATUS_LIST_MESSAGES=>"LIST_MESSAGES";
use constant STATUS_MESSAGE_COUNT=>"MESSAGE_COUNT";
use constant STATUS_DOWNLOAD_MESSAGE_HEADERS=>"DOWNLOAD_MESSAGE_HEADERS";
use constant STATUS_DOWNLOAD_MESSAGE_BODY=>"DOWNLOAD_MESSAGE_BODY";
use constant STATUS_MESSAGE_DOWNLOADED=>"MESSAGE_DOWNLOADED";
use constant STATUS_SUBSCRIBE=>"SUBSCRIBE";
use constant STATUS_UNSUBSCRIBE=>"UNSUBSCRIBE";
use constant STATUS_BOUNCE=>"BOUNCE";
use constant STATUS_FINISHED=>"FINISHED";
sub report_error{my($sock,$error)=@_;
utf8::encode($error);
$sock->write("ERROR: $error");}sub report_status{my$sock=shift@_;
my@args=map{defined($_)?$_:''}@_;
$sock->write("STATUS: ".join(',',map{$_=~s/"/\\"/g;utf8::encode($_);$_;}@args));}sub main{$|=1;
die if(scalar@ARGV<1);
my$sock=E3DS::HelperAppUnixServerSocket->new($ARGV[0]);
my($config_data_len,$config_data)=$sock->read();
if(!$config_data_len){die"Unable to read configuration data from host application.";}my$plist_string=NSString->alloc()->initWithCString_encoding_($config_data,4);
my$plist_string_data=$plist_string->dataUsingEncoding_(4);
my$plist=NSPropertyListSerialization->propertyListWithData_options_format_error_($plist_string_data,0,0,0);
my$config=undef;
if($plist&&$$plist){$config=Foundation::perlRefFromObjectRef($plist);}my$verbose=$config->{VerboseLogging}||0;
if($verbose){my$save_password=$config->{Password};
$config->{Password}="(hidden)";
print"Read config: ",Dumper($config),"\n";
$config->{Password}=$save_password;}my$scanner_package=(SCANNER_PACKAGES)[$config->{Protocol}];
my$scanner=$scanner_package->new();
my$error=undef;
report_status($sock,STATUS_CONNECTING);
if(!$scanner->connect($config->{Hostname},$config->{Port},$config->{UseSSL},$config->{User},$config->{Password},\$error)){report_error($sock,$error);
die$error;}report_status($sock,STATUS_CONNECTED);
report_status($sock,STATUS_LIST_MESSAGES);
my%message_number_to_uid;
my@candidate_message_nums=$scanner->getCandidateMessageNumbers($config,\%message_number_to_uid,\$error);
if(!@candidate_message_nums&&$error){report_error($sock,$error);
die$error;}print"Candidate message numbers: ",Dumper(\@candidate_message_nums),"\n" if$verbose;
print"Message number to UID: ",Dumper(\%message_number_to_uid),"\n" if$verbose;
report_status($sock,STATUS_MESSAGE_COUNT,scalar@candidate_message_nums);
my$total_handled=0;
my$limit=$config->{Limit};
for my $msg_num(@candidate_message_nums){my@req_headers=qw( Message-id Subject From Date );
my$headers_ref=undef;
my$message=undef;
report_status($sock,STATUS_DOWNLOAD_MESSAGE_HEADERS,$msg_num);
$headers_ref=$scanner->getMessageHeaders($msg_num,\@req_headers,\$error);
print"Couldn't get message headers: $error" if!$headers_ref&&$verbose;
next if!$headers_ref;
print"Headers: ",Dumper($headers_ref),"\n" if$verbose;
my$message_id=$headers_ref->{"Message-id"}->[0];
my$date=str2time($headers_ref->{"Date"}->[0]);
report_status($sock,STATUS_MESSAGE_DOWNLOADED,$message_id,$message_number_to_uid{$msg_num},$date);
my($subject,$from);
my$decoder=Email::MIME::RFC2047::Decoder->new();
my@addresses=Email::Address->parse($headers_ref->{From}->[0]);
print"Addresses: ",Dumper(\@addresses),"\n" if$verbose;
if(@addresses){$from=$addresses[0];
$from->phrase($decoder->decode_phrase($from->phrase));
print"From: ",$from->phrase," ",$from->address,"\n" if$verbose;}if(defined($headers_ref->{Subject})){$subject=$decoder->decode_text($headers_ref->{Subject}->[0]);
print"Subject: $subject","\n" if$verbose;}my$handled=0;
if($config->{LookForSubscribe}&&$subject&&$from){my$magic_phrase=$config->{SubscribePhrase};
if($subject=~m/\b$magic_phrase\b/i){print"Subscribe magic phrase found\n" if$verbose;
my$name=$from->phrase;
report_status($sock,STATUS_SUBSCRIBE,$message_id,$date,$from->address,$name);
if($config->{DeleteSubscribed}){print"Deleting message $msg_num\n" if$verbose;
$scanner->deleteMessage($msg_num);}$handled=1;}else{print"Subscribe magic phrase not found\n" if$verbose;}}if(!$handled&&$config->{LookForUnsubscribe}&&$subject&&$from){my$magic_phrase=$config->{UnsubscribePhrase};
if($subject=~m/\b$magic_phrase\b/i){print"Unsubscribe magic phrase found\n" if$verbose;
my$address_id=undef;
if($subject=~m/([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})/){print"Found address ID $1\n" if$verbose;
$address_id=$1;}report_status($sock,STATUS_UNSUBSCRIBE,$message_id,$date,$from->address,$address_id);
if($config->{DeleteUnsubscribed}){print"Deleting message $msg_num\n" if$verbose;
$scanner->deleteMessage($msg_num);}$handled=1;}else{print"Unsubscribe magic phrase not found\n" if$verbose;}}if(!$handled&&$config->{LookForBounced}&&$subject&&E3DS::BounceParser->subject_maybe_bounce_notification($subject)){report_status($sock,STATUS_DOWNLOAD_MESSAGE_BODY,$message_id);
my$message=$scanner->getRawMessage($msg_num,\$error);
if(!$message){print"Couldn't get message: $error\n" if$verbose;
next;}else{print"Looking for bounce in message $message\n" if$verbose;}my@bounce_reports=E3DS::BounceParser->parse({message_string=>$message,log=>sub{my$level=shift@_;print"[$level] ",@_,"\n" if$verbose;},verbose=>$verbose});
my$bounce_counter=0;
for my $bounce_report_ref(@bounce_reports){if(!$bounce_report_ref->{Email}){print"No bounced email address\n" if$verbose;
next;}my$std_reason=$bounce_report_ref->{StandardReason}||0;
my$hard_bounce=($std_reason eq 'user_unknown'||$std_reason eq 'domain_error')?1:0;
my%ids_by_reason=('user_unknown'=>1,'user_disabled'=>1,'over_quota'=>2,'domain_error'=>3,'domain_unknown'=>3,'spam'=>4,'message_too_large'=>5,'mail_loop'=>3,'unknown'=>0);
report_status($sock,STATUS_BOUNCE,$message_id,$bounce_report_ref->{Email},$bounce_report_ref->{DeliveryID}||0,$hard_bounce,$ids_by_reason{$std_reason}||0,$bounce_counter);
$bounce_counter+=1;}if($bounce_counter){$scanner->deleteMessage($msg_num)if$config->{DeleteBounced};
$handled=1;}}$total_handled+=1 if$handled;
last if(defined($limit)&&$total_handled>=$limit);}$scanner->disconnect();
report_status($sock,STATUS_FINISHED);
$sock->close();
return EX_OK;}exit(main());
